﻿/* *****************************************************************************
ShoutcastRunner 0.3

Copyright (c) KEXP 90.3 FM Seattle. All rights reserved. 
www.kexp.org
author: Louis O'Callaghan (louis@kexp.org)
October 2012

Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0  

THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
MERCHANTABLITY OR NON-INFRINGEMENT. 

See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.

SHOUTcast™ is a trademark of AOL LLC.
***************************************************************************** */

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using log4net;

namespace Kexp.ShoutcastRunner
{
	public static class ShoutcastLogParser
	{
		private static readonly ILog Log = LogManager.GetLogger(typeof(ShoutcastLogParser));

		/// <summary>
		/// The plan: go through each line in the specified log file. Check the date of each
		/// line against a dictionary of dates and file handles. If the date exists, append the
		/// line to the file handle. If the date doesn't exist, look for an existing file with
		/// the expected output filename. If the file doesn't exist, create it, add to dictionary
		/// and append the line. If the file exists, check its size. If it's small then open it 
		/// for overwriting, add it to the dictionary and append the line. If it's big, then 
		/// skip the line
		/// </summary>
		/// <param name="filename"></param>
		/// <param name="overwriteSizeLimit">Overwrite existing files below this size. Default is 500K</param>
		/// <returns></returns>
		public static bool ParseDailyLogs(string filename, int overwriteSizeLimit = Settings.DefaultOverwriteLogSizeLimit)
		{
			var logInfo = new FileInfo(filename);
			if (!logInfo.Exists)
			{
				Log.Error(string.Format("Can't find {0} to parse.", logInfo.Name));
				return false;
			}

			if (logInfo.IsW3CLog())
			{
				Log.Error("Wrong log type for this parser.");
				return false;
			}

			using (var fileStream = new FileStream(logInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
			{
				using (var streamReader = new StreamReader(fileStream))
				{
					var outputDictionary = new Dictionary<DateTime, TextWriter>();
					var datesToSkip = new List<DateTime>();
					DateTime? currentWorkingDate = null;
					DateTime adjustedNow = Helpers.GetAdjustedNowDate();

					while (streamReader.Peek() >= 0)
					{
						var line = new Line(streamReader.ReadLine());

						var lineDate = line.GetDate();
						if (lineDate == null ||
							// if this is running just after midnight, don't create a file for today:
							lineDate.Value.Date > adjustedNow.Date ||
							datesToSkip.Any(d => d.Date == lineDate.Value.Date))
							continue;

						// since line dates will be sequential, we should flush the previous file
						// when the date changes
						if (currentWorkingDate == null || currentWorkingDate.Value.Date != lineDate.Value.Date)
						{
							var previousFile = currentWorkingDate != null
							                   && outputDictionary.ContainsKey(currentWorkingDate.Value.Date)
							                   	? outputDictionary[currentWorkingDate.Value.Date]
							                   	: null;

							if (previousFile != null)
								previousFile.Flush();

							currentWorkingDate = lineDate.Value.Date;
						}

						var outputFile = outputDictionary.ContainsKey(lineDate.Value.Date)
						                 	? outputDictionary[lineDate.Value.Date]
						                 	: null;

						if (outputFile == null)
						{
							var datedFileName = Helpers.GetDatedFileName(logInfo.Name, lineDate.Value);
							var outputFilePath = Path.Combine(logInfo.DirectoryName, datedFileName);
							var outputFileInfo = new FileInfo(outputFilePath);

							if (outputFileInfo.Exists)
							{
								if (outputFileInfo.Length > overwriteSizeLimit)
								{
									datesToSkip.Add(lineDate.Value.Date);
									continue;
								}
								outputFileInfo.CopyTo(outputFileInfo.Name + DateTime.Now.Ticks);
								outputFileInfo.Delete();
							}

							outputFile = outputFileInfo.CreateText();
							outputDictionary.Add(lineDate.Value.Date, outputFile);
						}

						outputFile.WriteLine(line.ToString());
					}

					// cleanup
					foreach (var file in outputDictionary)
					{
						TextWriter writer = file.Value;
						if (writer != null)
						{
							writer.Close();
							writer.Dispose();
						}
					}
				}
			}

			return true;
		}

		// example: <09/05/12@13:00:28> ..... other stuff .....
		public class Line
		{
			private readonly string _line;

			public Line(string line)
			{
				_line = line;
			}

			/// <summary>
			/// Not Y2.1K compatible
			/// </summary>
			/// <returns></returns>
			public DateTime? GetDate()
			{
				// example: <09/05/12@13:00:28> ..... other stuff .....
				string dateTimePart = _line.Substring(1, 17);

				string monthPart = dateTimePart.Substring(0, 2);
				string dayPart = dateTimePart.Substring(3, 2);
				string yearPart = dateTimePart.Substring(6, 2);
				string hourPart = dateTimePart.Substring(9, 2);
				string minutePart = dateTimePart.Substring(12, 2);
				string secondsPart = dateTimePart.Substring(15, 2);

				int month, day, year, hour, minute, second;

				// here's why this won't work next century
				yearPart = "20" + yearPart;

				if (!int.TryParse(monthPart, out month) ||
					!int.TryParse(dayPart, out day) ||
					!int.TryParse(yearPart, out year) ||
					!int.TryParse(hourPart, out hour) ||
					!int.TryParse(minutePart, out minute) ||
					!int.TryParse(secondsPart, out second))
					return null;

				return new DateTime(year, month, day, hour, minute, second);
			}

			public new string ToString()
			{
				return _line;
			}
		}
	}
}
